package run.microservice.stock.utils;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ThreadLocalRandom;

/**
 * ULID生成器。通用唯一字典顺序可排序标识符；长度26位
 * ULID规范：https://github.com/ulid/spec
 * 来源：https://gitee.com/mybatis-flex/mybatis-flex/blob/main/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/impl/ULIDKeyGenerator.java
 * @author lunfy
 * @description:
 * @date 2024-04-23 17:32
 */
@Slf4j
public class ULIDWorker {

    private ULIDWorker() {}

    private static class SingletonHolder {
        private static final ULIDWorker INSTANCE = new ULIDWorker();
    }
    public static ULIDWorker getInstance() {
        return ULIDWorker.SingletonHolder.INSTANCE;
    }
    /** 防止序列化破坏单例 */
    protected Object readResolve() {
        return SingletonHolder.INSTANCE;
    }

    private static final char[] ENCODING_CHARS = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
            'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X',
            'Y', 'Z'
    };

    private static final long TIMESTAMP_OVERFLOW_MASK = 0xFFFF_0000_0000_0000L;

    private static final ThreadLocal<StringBuilder> THREAD_LOCAL_BUILDER =
            ThreadLocal.withInitial(() -> new StringBuilder(26));

    private long lastTimestamp = 0;

    private long lastRandom = 0;

    /**
     * 生成一个 ULID
     *
     * @return ULID
     */
    public String nextId() {
        return generateULID(System.currentTimeMillis()).toUpperCase();
    }

    /**
     * 生成一个严格单调的 ULID
     *
     * @return ULID
     */
    public synchronized String nextMonotonicId() {
        long timestamp = System.currentTimeMillis();
        if (timestamp > lastTimestamp) {
            lastTimestamp = timestamp;
            lastRandom = ThreadLocalRandom.current().nextLong();
        } else {
            lastRandom++;
            if (lastRandom == 0) {
                timestamp = waitNextMillis(lastTimestamp);
                lastTimestamp = timestamp;
                lastRandom = ThreadLocalRandom.current().nextLong();
            }
        }
        return generateULID(lastTimestamp, lastRandom).toUpperCase();
    }

    private String generateULID(long timestamp) {
        return generateULID(timestamp, ThreadLocalRandom.current().nextLong());
    }

    private String generateULID(long timestamp, long random) {
        checkTimestamp(timestamp);
        StringBuilder builder = THREAD_LOCAL_BUILDER.get();
        builder.setLength(0);

        appendCrockford(builder, timestamp, 10);
        appendCrockford(builder, random, 16);

        return builder.toString();
    }

    private long waitNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    private static void appendCrockford(StringBuilder builder, long value, int count) {
        for (int i = (count - 1) * 5; i >= 0; i -= 5) {
            int index = (int) ((value >>> i) & 0x1F);
            builder.append(ENCODING_CHARS[index]);
        }
    }

    private static void checkTimestamp(long timestamp) {
        if ((timestamp & TIMESTAMP_OVERFLOW_MASK) != 0) {
            throw new IllegalArgumentException("ULID does not support timestamps after +10889-08-02T05:31:50.655Z!");
        }
    }

    public static void main(String[] args) {
        ULIDWorker ulidWorker = new ULIDWorker();
        for (int i = 0; i < 3000; i++) {
            log.info("生成的ID：{}", ULIDWorker.getInstance().nextMonotonicId());
        }

    }
}
